/****************************************************************************
 **
 ** This file is part of the yFiles extension package GraphML-3.2-yFiles-2.7.
 ** 
 ** yWorks proprietary/confidential. Use is subject to license terms.
 **
 ** Redistribution of this file or of an unauthorized byte-code version
 ** of this file is strictly forbidden.
 **
 ** Copyright (c) 2000-2009 by yWorks GmbH, Vor dem Kreuzberg 28, 
 ** 72070 Tuebingen, Germany. All rights reserved.
 **
 ***************************************************************************/

package demo.yext.graphml;

import org.graphdrawing.graphml.GraphMLConstants;
import org.graphdrawing.graphml.reader.dom.DOMGraphMLParseContext;
import org.graphdrawing.graphml.reader.dom.DOMInputHandler;
import org.graphdrawing.graphml.writer.GraphMLWriteContext;
import org.graphdrawing.graphml.writer.OutputHandler;
import org.graphdrawing.graphml.writer.XmlWriter;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import y.base.DataMap;
import y.base.Graph;
import y.base.NodeMap;
import yext.graphml.graph2D.GraphMLIOHandler;
import yext.graphml.reader.AbstractDOMInputHandler;
import yext.graphml.writer.AbstractOutputHandler;

import java.util.StringTokenizer;

/**
 * IOHandler for serializing graphs in GraphML format with complex extensions.
 * <p>
 * The node labels of the graph are serialized as lists.
 * Each token of the label corresponds to a list item.
 * </p>
 */
public class ComplexExtensionGraphMLIOHandler extends GraphMLIOHandler {
  /////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////    Init    /////////////////////////////////
  /////////////////////////////////////////////////////////////////////////////  
  NodeMap nodeDataMap;
  DataMap graphDataMap;


  /**
   * Creates a new instance of the IOHandler.
   */
  public ComplexExtensionGraphMLIOHandler(NodeMap nodeDataMap, DataMap graphDataMap) {
    this.nodeDataMap = nodeDataMap;
    this.graphDataMap = graphDataMap;

    /** definition of handlers for extensions */
    addInputHandler(new MyNodeInputHandler());
    addOutputHandler(new MyNodeOutputHandler(), GraphMLConstants.SCOPE_NODE);
//    getOutputHandlers( GraphMLConstants.SCOPE_NODE ).add( new MyNodeOutputHandler() );

    GraphAttributeIOHandler graphAttributeIOHandler = new GraphAttributeIOHandler();
    addInputHandler(graphAttributeIOHandler);
    addOutputHandler(graphAttributeIOHandler, GraphMLConstants.SCOPE_GRAPH);
  }

  /////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////    Parsing    ///////////////////////////
  /////////////////////////////////////////////////////////////////////////////

  class MyNodeInputHandler extends AbstractDOMInputHandler {

    public boolean acceptKey(NamedNodeMap map, int scopeType) {
      // accept only data-tags which have the extra XML-Attribute extension.type with value list. 
      Node node = map.getNamedItem("extension.type");
      if (node == null) {
        return false;
      }
      if ("list".equals(node.getNodeValue())) {
        return true;
      } else {
        return false;
      }
    }


    /**
     * Parses the data.
     *
     * @param context     The context in which the parser is.
     * @param graph       The current graph in the parse context.
     * @param nodeedge    The current node/edge in the parse context.
     * @param defaultMode Whether default data or real data is read.
     * @param domNode     The DOM node which is actually processed.
     */
    protected void parseData(DOMGraphMLParseContext context,
                             Graph graph, Object nodeedge,
                             boolean defaultMode,
                             org.w3c.dom.Node domNode) {
      // default value handling not implemented
      if (defaultMode) {
        return;
      }

      org.w3c.dom.NodeList children = domNode.getChildNodes();
      if (children != null) {
        for (int i = 0; i < children.getLength(); i++) {
          org.w3c.dom.Node n = children.item(i);
          if (n.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
            String name = n.getLocalName();
            if ("ItemList".equals(name)) {
              String items = "";
              org.w3c.dom.NodeList children2 = n.getChildNodes();
              if (children2 != null) {
                for (int i2 = 0; i2 < children2.getLength(); i2++) {
                  org.w3c.dom.Node n2 = children2.item(i2);
                  if (n2.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
                    if ("Item".equals(n2.getLocalName())) {
                      org.w3c.dom.NodeList children3 = n2.getChildNodes();
                      for (int i3 = 0; i3 < children3.getLength(); i3++) {
                        if (children3.item(i3).getNodeType() == Node.TEXT_NODE) {
                          items += children3.item(i3).getNodeValue() + "\n";
                        }
                      }
                    }
                  }
                }
              }
              if (items.length() > 0) {
                nodeDataMap.set(nodeedge, items.substring(0, items.length() - 1));
              }
            }
          }
        }
      }
    }

    /**
     * Parses the data.
     *
     * @param context  The context in which the parser is.
     * @param graph    The current graph in the parse context.
     * @param nodeedge The current node/edge in the parse context.
     */
    protected void applyDefault(DOMGraphMLParseContext context,
                                Graph graph, Object nodeedge) {
      // default value handling not implemented
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////    Writing    ///////////////////////////
  /////////////////////////////////////////////////////////////////////////////

  class MyNodeOutputHandler extends AbstractOutputHandler {
    public void printKeyAttributes(GraphMLWriteContext context, XmlWriter writer) {
      // mark with type declaration
      writer.writeAttribute("extension.type", "list");
    }

    public void printDataOutput(GraphMLWriteContext context, Graph graph, Object nodeedge, XmlWriter writer) {
      writer.writeStartElement(null, "ItemList", null);

      String items = (String) nodeDataMap.get(nodeedge);
      if (items != null) {
        StringTokenizer tok = new StringTokenizer(items, "\n\r");
        while (tok.hasMoreTokens()) {
          String item = tok.nextToken();
          writer.writeStartElement("Item", null)
              .writeText(item)
              .writeEndElement();
        }
      }
      writer.writeEndElement();
    }

    // Writes default value, not used yet
    public void printKeyOutput(GraphMLWriteContext context, XmlWriter writer) {
      // default value handling not implemented
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  ///////////////////////////// Graph Attributes /////////////////////////////
  ////////////////////////////////////////////////////////////////////////////

  class GraphAttributeIOHandler implements OutputHandler, DOMInputHandler {
    // Output

    public void printKeyAttributes(GraphMLWriteContext context, XmlWriter writer) {
      writer.writeAttribute("type", "MyGraphAttribute");
    }

    public void printKeyOutput(GraphMLWriteContext context, XmlWriter writer) {
    }

    public void printDataAttributes(GraphMLWriteContext context, XmlWriter writer) {
    }

    public void printDataOutput(GraphMLWriteContext context, XmlWriter writer) {
      Object graph = context.getCurrentContainer();
      String data = (String) graphDataMap.get(graph);
      if (data != null) {
        writer.writeText(data);
      }
    }

    // Input

    public boolean acceptKey(NamedNodeMap attributes, int scopeType) {
      // accept only data-tags which have the extra XML-Attribute extension.type with value list. 
      Node node = attributes.getNamedItem("type");
      if (node == null) {
        return false;
      }
      if (!"MyGraphAttribute".equals(node.getNodeValue())) {
        return false;
      } else {
        return true;
      }
    }

    public void parseData(DOMGraphMLParseContext context, boolean defaultMode, Node node) {
      if (defaultMode) {
        return;
      }

      Object graph = context.getCurrentContainer();
      org.w3c.dom.NodeList children = node.getChildNodes();
      if (children != null) {
        for (int i = 0; i < children.getLength(); i++) {
          org.w3c.dom.Node n = children.item(i);
          if (n.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
            graphDataMap.set(graph, children.item(i).getNodeValue());
          }
        }
      }
    }

    public void applyDefault(DOMGraphMLParseContext context) {
    }
  }
}
